package fr.noe.amicaleloisirs.concours.business;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Window;

import fr.noe.amicaleloisirs.concours.interfaces.CallbackHandler;
import fr.noe.amicaleloisirs.concours.utils.Constants.Tour;
import fr.noe.amicaleloisirs.concours.utils.Constants;
import fr.noe.amicaleloisirs.concours.utils.DateUtil;
import fr.noe.amicaleloisirs.concours.utils.FileSystem;
import fr.noe.amicaleloisirs.concours.utils.Logger;
import fr.noe.amicaleloisirs.concours.utils.XMLHelper;
import fr.noe.amicaleloisirs.concours.view.ConfirmPopUp;

/**
 * 
 * @author Sébastien PREVOST
 *
 */
public class Concours {
	
	protected static Concours instance = null;

	private String date;
	protected List<Team> teams;
	protected Tour currentTour;
	protected List<Match> matches;

	protected Concours(String date) {
		this.date = date;
		this.teams = new ArrayList<Team>();
		this.currentTour = Tour.INIT;
		matches = new ArrayList<Match>();
	}
	
	public static Concours get() {
		if (instance == null) {
			
			// vu que le concours commence le samedi soir et fini le dimanche, on doit tester les 2 dates
			String concoursName = DateUtil.createDateToday();
			String yesterday = DateUtil.createDateYesterday();
			
			// si un concours déjà présent pour cette date => on a sauvé puis quitter...
			if (FileSystem.get().fileExists(Constants.CONCOURS_URL_FILES + concoursName + ".xml"))
				chargerConcoursExistant(concoursName);
			else if (FileSystem.get().fileExists(Constants.CONCOURS_URL_FILES + yesterday + ".xml"))
				chargerConcoursExistant(yesterday);
			else
				instance = new Concours(concoursName);
		}
		return instance;
	}
	
	private static void log(String text) {
		Logger.get().logWithClass("Concours", text);
	}

	public String getDate() {
		return date;
	}

	public List<Team> getTeams() {
		return teams;
	}

	public boolean addTeam(Team t) {
		return teams.add(t);
	}

	public Tour getTour() {
		return currentTour;
	}
	
	//TODO: remove me
	public void setTour(Tour t) {
		currentTour = t;
	}
	
	public List<Match> getMatches() {
		return matches;
	}
	
	/**
	 * Methode qui ajoute une nouvelle équipe après le début d'un concours.
	 * Si une equipe est exempte, elle joue contre celle que l'on veut ajouter,
	 * sinon la nouvelle est exempte
	 * 
	 * @param t : l'équipe à ajouter au concours
	 * @return <code>true</code> si ok
	 */
	public boolean addTeamAfterBeginning(Team t) {
		if (currentTour != Tour.INIT) {
			Match m = null;
			for (Match cur : matches) {
				if (!cur.isMatchWithTwoTeams()) {
					m = cur;
					break;
				}
			}
			
			// si il n'y a pas d'équipes exemptes
			if (m == null) {
				m = new Match(t, null);
				matches.add(m);
			}
			
			// si équipe exempte, celle qu'on ajoute la rencontre
			else {
				if (m.getT1() == null)
					m.setT1(t);
				else
					m.setT2(t);
			}
		}
		return teams.add(t);
		
	}
	
	public int getIntTour() {
		switch (currentTour) {
		case INIT:
			return 0;
		case UN:
			return 1;
		case DEUX:
			return 2;
		case TROIS:
			return 3;
		case QUATRE:
			return 4;

		default:
			return -1;
		}
	}
	
	public Tour getTourFromInt(int tour) {
		switch (tour) {
		case 1:
			return Tour.UN;
		case 2:
			return Tour.DEUX;
		case 3:
			return Tour.TROIS;
		case 4:
			return Tour.QUATRE;
		}
		return Tour.INIT;
	}

	/**
	 * Methode a appeler une fois toutes les parties d'un tour finies.
	 *
	 * @return <code>true</code> si OK, <code>false</code> si tous
	 * les résultats du tour ne sont pas encore renseignés.
	 *
	 */
	public boolean nextTour() {

		if (currentTour == Tour.QUATRE) {
			if (checkAllTeamsHaveNbTour(4)) {
				log("nextTour() toutes les équipes ont 4 tours, concours fini");
				return true;
			}

			// quatrième tour mais toutes les équipes n'ont pas 4 résultats
			log("nextTour() tour 4 mais toutes les équipes n'ont pas 4 tours : erreur");
			return false;
		}
		int tour;
		Tour suivant;
		if (currentTour == Tour.INIT) {
			boolean isOk = tirerParties();
			currentTour = Tour.UN;
			log("nextTour() tour INIT, tirage : " + isOk);
			return isOk;
		}
		if (currentTour == Tour.UN) {
			tour = 1;
			suivant = Tour.DEUX;
		}
		else if (currentTour == Tour.DEUX) {
			tour = 2;
			suivant = Tour.TROIS;
		}
		else { //if (currentTour == Tour.TROIS) {
			tour = 3;
			suivant = Tour.QUATRE;
		}
		if (checkAllTeamsHaveNbTour(tour)) {
			boolean isTirageOk = tirerParties();
			log("nextTour() tour " + tour + ", tirage : " + isTirageOk);
			currentTour = suivant;
			return isTirageOk;
		}
		return false;
	}

	public boolean checkAllTeamsHaveNbTour(int nbTour) {
		for (Team t : teams) {
			if (t.getSerie().length() != nbTour)
				return false;
		}
		return true;
	}

	private boolean tirerParties() {
		matches = new ArrayList<Match>();
		List<Team> list = new ArrayList<Team>();
		list.addAll(teams);
		if (currentTour == Tour.INIT) {
			for (int i = 0 ; i < (teams.size() - 1) ; i = i + 2) {
				// code GWT
				int index = Random.nextInt(list.size());
				// code Java pour test
//				int index = (new java.util.Random()).nextInt(list.size());
				Team t1 = list.get(index);
				list.remove(index);
				// code GWT
				index = Random.nextInt(list.size());
				// code Java pour test
//				index = (new java.util.Random()).nextInt(list.size());
				Team t2 = list.get(index);
				list.remove(index);
				matches.add(new Match(t1, t2));
			}

			// nombre pair d'équipes: tout le monde joue
			if (list.size() == 0) {
				log("Tour initial avec un nombre pair d'équipes");
				return true;
			}

			// nombre impair: il doit rester une équipe
			else if (list.size() == 1) {
				log("Tour initial avec un nombre impair d'équipes");
				matches.add(new Match(list.get(0), null));
				return true;
			}

			else {
				log("Erreur: il reste " + list.size() + " equipes");
				return false;
			}

		}
		else {
			// nombre impair
			if ((list.size() % 2) == 1) {
				List<Team> listeClassee = getClassement();
				List<Team> classeeEtAvecDernierJamaisExempt = trierPourNombreImpair(listeClassee);
				List<Team> jamaisRencontre = trierPourMatchJamaisFait(classeeEtAvecDernierJamaisExempt.subList(0, list.size() - 1));
				boolean isOk = creerMatchesListePair(jamaisRencontre);
				
				// dernier au classement est exempt.
				isOk = isOk && matches.add(new Match(listeClassee.get(listeClassee.size() - 1), null));
				log("Tour " + currentTour + " avec un nombre impair d'équipes, tirage : " + isOk);
				return isOk;
			}
			else {
				List<Team> listeClassee = getClassement();
				boolean isOk = creerMatchesListePair(trierPourMatchJamaisFait(listeClassee));
				log("Tour " + currentTour + " avec un nombre pair d'équipes, tirage : " + isOk);
				return isOk;
			}
		}
	}
	
	/**
	 * Trier la liste en parametre de telle sorte que chaque match 
	 * ne se soit jamais produit.
	 */
	private List<Team> trierPourMatchJamaisFait(List<Team> aTrier) {
		List<Team> tmp = new ArrayList<Team>();
		tmp.addAll(aTrier);
		boolean tiragesPossibles = false;
		int cpt = 0;
		while (!tiragesPossibles && (cpt < 6)) {
			boolean testOk = true;
			for (int i = 0 ; i < (tmp.size() - 1) ; i = i + 2) {
				if (tmp.get(i).isDejaRencontree(tmp.get(i + 1).getTeamId())) {
					if ((i + 2) < tmp.size()) {
						Team t = tmp.remove(i + 1);
						tmp.add(i+2, t);
					}
					testOk = false;
				}
			}
			cpt = cpt + 1;
			tiragesPossibles = testOk;
		}
		// si on tourne en boucle sans arriver à trouver
		if (cpt == 6) {
			cpt = 0;
			while (!tiragesPossibles && (cpt < 6)) {
				boolean testOk = true;
				for (int i = (tmp.size() - 1) ; i > 0  ; i = i - 2) {
					if (tmp.get(i).isDejaRencontree(tmp.get(i - 1).getTeamId())) {
						if ((i - 2) < tmp.size()) {
							Team t = tmp.remove(i - 1);
							tmp.add(i - 2, t);
						}
						testOk = false;
					}
				}
				cpt = cpt + 1;
				tiragesPossibles = testOk;
			}
			return aTrier;
		}
		return tmp;
	}
	
	private boolean creerMatchesListePair(List<Team> list) {
		for (int i = 0 ; i < list.size() ; i = i + 2) {
			Team t1 = list.get(i);
			Team t2 = list.get(i + 1);
			matches.add(new Match(t1, t2));
		}
		return true;
	}
	
	public List<Team> getClassement() {
		if (currentTour == Tour.INIT) {
			return new ArrayList<Team>();
		}
		else if (currentTour == Tour.UN) {
			List<Team> gagnants = new ArrayList<Team>();
			List<Team> perdants = new ArrayList<Team>();
			for (Team t : teams) {
				if (t.getNombreVictoires() == 1) {
					gagnants.add(t);
				}
				else {
					perdants.add(t);
				}
			}
			List<Team> res = trierParPoint(gagnants);
			res.addAll(trierParPoint(perdants));
			return res;
		}
		else if (currentTour == Tour.DEUX) {
			List<Team> gagnants2 = new ArrayList<Team>();
			List<Team> gagnants = new ArrayList<Team>();
			List<Team> perdants = new ArrayList<Team>();
			for (Team t : teams) {
				if (t.getNombreVictoires() == 2) {
					gagnants2.add(t);
				}
				else if (t.getNombreVictoires() == 1) {
					gagnants.add(t);
				}
				else {
					perdants.add(t);
				}
			}
			List<Team> res = trierParPoint(gagnants2);
			res.addAll(trierParPoint(gagnants));
			res.addAll(trierParPoint(perdants));
			return res;
		}
		else if (currentTour == Tour.TROIS) {
			List<Team> gagnants3 = new ArrayList<Team>();
			List<Team> gagnants2 = new ArrayList<Team>();
			List<Team> gagnants = new ArrayList<Team>();
			List<Team> perdants = new ArrayList<Team>();
			for (Team t : teams) {
				if (t.getNombreVictoires() == 3) {
					gagnants3.add(t);
				}
				else if (t.getNombreVictoires() == 2) {
					gagnants2.add(t);
				}
				else if (t.getNombreVictoires() == 1) {
					gagnants.add(t);
				}
				else {
					perdants.add(t);
				}
			}
			List<Team> res = trierParPoint(gagnants3);
			res.addAll(trierParPoint(gagnants2));
			res.addAll(trierParPoint(gagnants));
			res.addAll(trierParPoint(perdants));
			return res;
		}
		else {

			List<Team> gagnants4 = new ArrayList<Team>();
			List<Team> gagnants3 = new ArrayList<Team>();
			List<Team> gagnants2 = new ArrayList<Team>();
			List<Team> gagnants = new ArrayList<Team>();
			List<Team> perdants = new ArrayList<Team>();
			for (Team t : teams) {
				if (t.getNombreVictoires() == 4) {
					gagnants4.add(t);
				}
				else if (t.getNombreVictoires() == 3) {
					gagnants3.add(t);
				}
				else if (t.getNombreVictoires() == 2) {
					gagnants2.add(t);
				}
				else if (t.getNombreVictoires() == 1) {
					gagnants.add(t);
				}
				else {
					perdants.add(t);
				}
			}
			List<Team> res = trierParPoint(gagnants4);
			res.addAll(trierParPoint(gagnants3));
			res.addAll(trierParPoint(gagnants2));
			res.addAll(trierParPoint(gagnants));
			res.addAll(trierParPoint(perdants));
			return res;
		
		}
	}
	
	/**
	 * Permet de trier une liste d'equipes par point
	 * (les équipes doivent avoir le même nombre de victoires)
	 */
	public List<Team> trierParPoint(List<Team> listeATrier) {
		List<Team> res = new ArrayList<Team>();
		List<Team> tmp = new ArrayList<Team>();
		tmp.addAll(listeATrier);
		while (res.size() != listeATrier.size()) {
			Team toAdd = tmp.get(0);
			for (Team t : tmp) {
				if (t.getTotalPour() > toAdd.getTotalPour()) {
					toAdd = t;
				}
				else if (t.getTotalPour() == toAdd.getTotalPour()) {
					if (t.getTotalContre() < toAdd.getTotalContre()) {
						toAdd = t;
					}
				}
			}
			res.add(toAdd);
			tmp.remove(toAdd);
		}
		return res;
	}
	
	/**
	 * Trie la liste en paramètre en vérifiant que le dernier n'ait
	 * jamais été exempt.
	 * @return la liste avec le dernier de la liste qui n'a jamais été exempt.
	 */
	private List<Team> trierPourNombreImpair(List<Team> liste) {
		int idMettreEnDernier = liste.size() - 1;
		List<Team> res = new ArrayList<Team>();
		res.addAll(liste);
		while(res.get(idMettreEnDernier).hasAlreadyBeenExempt()) {
			idMettreEnDernier = idMettreEnDernier - 1;
		}
		Team mettreEnDernier = res.get(idMettreEnDernier);
		res.remove(idMettreEnDernier);
		res.add(mettreEnDernier);
		return res;
	}

	/**
	 * Charger un concours déjà sauvé si validation de l'utilisateur.
	 */
	public static void chargerConcoursExistant(final String date) {
		new ConfirmPopUp("Un concours daté du " + date + " existe déjà, cliquez sur Valider<br>" +
				" pour le charger, ou Annuler pour en créer un nouveau<br>" +
				" (attention si vous cliquez sur Annuler, les données du précédent concours seront perdues)", new CallbackHandler() {
					
			@Override
			public void onValidate() {
				//validation = on ne fait rien, sachant que par défaut on charge le concours existant.
				log("chargerConcoursExistant à la date " + date + " validé");
			}
			
			@Override
			public void onCancel() {
				log("chargerConcoursExistant à la date " + date + " annulé");
				
				// si on annule, on supprime les données du concours existant, et on recharge la page.
				FileSystem.get().fileDelete(Constants.CONCOURS_URL_FILES + "" + date + ".xml");
				Window.Location.reload();
			}
		});
		instance = XMLHelper.get().loadConcours(Constants.CONCOURS_URL_FILES + "" + date + ".xml");
	}
	
	/**
	 * Sauvegarde du concours.
	 * @return true si save ok
	 */
	public boolean sauverConcours() {
		log("sauvegarde automatique du concours " + date);
		return XMLHelper.get().saveConcours(Constants.CONCOURS_URL_FILES + "" + date + ".xml", this);
	}


}
